//
// Created by zhengbo on 23-4-18.
//

#ifndef VISUALIZATION_TOOL_CAMERA_H
#define VISUALIZATION_TOOL_CAMERA_H

#include <Eigen/Geometry>

class Camera {
public:
    Camera();

    // Camera position
    Eigen::Vector3f position;

    // Camera rotation as quaternion
    Eigen::Quaternionf rotation;

    // Camera view matrix
    Eigen::Matrix4f viewMatrix;

    // Camera projection matrix
    Eigen::Matrix4f projectionMatrix;

    // Recalculate the view and projection matrices
    void update();

    // Set an orthographic projection
    void setOrthoProjection(float left, float right, float bottom, float top, float near, float far);

    // Set a perspective projection
    void setPerspectiveProjection(float fov, float aspect, float near, float far);

    // Get the view projection matrix (view * projection)
    Eigen::Matrix4f getViewProjectionMatrix();

    // Zoom the camera (change field of view)
    void zoom(float amount);

    // Translate the camera
    void translate(Eigen::Vector3f translation);

    // Rotate the camera
    void rotate(Eigen::Vector3f axis, float angle);

private:
    // Projection type (orthographic or perspective)
    ProjectionType type;
    float left;
    float right;
    float bottom;
    float top;
    float near;
    float far;
    float fov;
    float aspect;
};

Camera::Camera() {
    position = Eigen::Vector3f(0.0f, 0.0f, 0.0f);
    rotation = Eigen::Quaternionf(1.0f, 0.0f, 0.0f, 0.0f);
    type = ProjectionType::Perspective;
}

void Camera::update() {
    // Calculate view matrix
    viewMatrix = rotation.toRotationMatrix();
    viewMatrix.translate(-position);

    // Calculate projection matrix
    if (type == ProjectionType::Orthographic) {
        projectionMatrix = Eigen::Matrix4f::Ortho(left, right, bottom, top, near, far);
    } else {
        projectionMatrix = Eigen::Matrix4f::Perspective(fov, aspect, near, far);
    }
}

void Camera::setOrthoProjection(float left, float right, float bottom, float top, float near, float far) {
    type = ProjectionType::Orthographic;
    this->left = left;
    this->right = right;
    this->bottom = bottom;
    this->top = top;
    this->near = near;
    this->far = far;
    update();
}

void Camera::setPerspectiveProjection(float fov, float aspect, float near, float far) {
    type = ProjectionType::Perspective;
    this->fov = fov;
    this->aspect = aspect;
    this->near = near;
    this->far = far;
    update();
}

Eigen::Matrix4f Camera::getViewProjectionMatrix() {
    return projectionMatrix * viewMatrix;
}
#endif //VISUALIZATION_TOOL_CAMERA_H